package com.explorex.infra.puma.gateway.netty.common;

import com.explorex.infra.puma.gateway.netty.common.exception.SocketRuntimeException;
import com.explorex.infra.puma.gateway.netty.ws.handler.HeartbeatIdleHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;


/**
 * @author: JacceYang
 * @createTime: 2022/07/14 7:40 下午
 * @description: netty server base class for initial a netty server for different server type
 */
@Slf4j
public abstract class AbstractBaseServer extends BaseChannel {
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private ServerBootstrap serverBootstrap;
    @Setter
    @Getter
    private boolean sslClientAuth = false;

    public AbstractBaseServer() {
        this.setHost("0.0.0.0");
    }

    @Override
    protected void init() {
        serverBootstrap = new ServerBootstrap();
    }

    public AbstractBaseServer self() {
        return this;
    }

    public boolean useEpoll() {
        String osName = System.getProperty("os.name");
        boolean isLinuxPlatform = osName.toLowerCase().contains("linux");
        return isLinuxPlatform && Epoll.isAvailable();
    }

    private void initGroup() {
        if (useEpoll()) {
            log.info("used Epoll!!!,workerCount={}",workerCount);
            bossGroup = new EpollEventLoopGroup(2);
            workerGroup = new EpollEventLoopGroup(workerCount);
        } else {
            bossGroup = new NioEventLoopGroup(1);
            workerGroup = new NioEventLoopGroup(workerCount);
        }

    }

    public void bind(int port) {
        this.setPort(port);
        this.bind();
    }

    protected void initBind() {

    }

    public void bind() {
        initBind();
        initGroup();
        try {
            serverBootstrap.option(ChannelOption.SO_KEEPALIVE, keepAlive);
            serverBootstrap.option(ChannelOption.TCP_NODELAY, tcpNoDelay);
            serverBootstrap.option(ChannelOption.SO_BACKLOG, soBacklog);
            initServerBootStrap(serverBootstrap);
            serverBootstrap.group(bossGroup, workerGroup);

            serverBootstrap.channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class);

            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    if (self().isCheckHeartbeat()) {
                        ch.pipeline().addLast("heartbeat idle", new HeartbeatIdleHandler(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds, TimeUnit.SECONDS));
                    }

                    initSocketChannel(ch);
                }
            });

            ChannelFuture future = serverBootstrap.bind(this.getHost(), this.getPort());
            channel = future.channel();
            future.sync();

        } catch (Exception e) {
            log.info("WebSocket launched failed ", e);
            throw new SocketRuntimeException(e);
        } finally {
            if (channel != null && channel.isActive()) {
                log.info("Netty server listening " + this.getHost() + " on port " + this.getPort()
                        + " and ready for connections...");
            }
        }
    }

    protected void initServerBootStrap(ServerBootstrap serverBootstrap) {
    }

    protected void initSocketChannel(SocketChannel ch) {
    }

    @Override
    public void shutdown() {
        if (channel != null) {
            channel.close().syncUninterruptibly();
        }
        if (workerGroup != null) {
            workerGroup.shutdownGracefully().syncUninterruptibly();
        }
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().syncUninterruptibly();
        }
    }

}
